package com.topnav.mc.admin.security.config;

import com.topnav.mc.admin.security.filter.CutomFilterSecurityInterceptor;
import com.topnav.mc.admin.security.filter.VerifyCodeFilter;
import com.topnav.mc.admin.security.jwt.JwtUserDetailService;
import com.topnav.mc.admin.security.jwt.handle.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;

import javax.annotation.Resource;
import javax.servlet.Filter;

/**
 * 说明：
 * @类名: WebSecurityConfig
 * <p>
 *
 * </p>
 * @author   kenny
 * @Date	 2022年2月11日下午6:56:11
 */

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private Filter uriFormatFilter;

    @Resource
    private LoginSuccessHandler loginSuccessHandler;
    @Resource
    private LoginFailureHandler loginFailureHandler;
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Resource
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;
    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Autowired
    private JwtUserDetailService userDetailService;
    @Resource
    private CutomFilterSecurityInterceptor cutomFilterSecurityInterceptor;
    @Resource
    private JwtLogoutSuccessHandler jwtLogoutSuccessHandler;
    @Resource
    private VerifyCodeFilter verifyCodeFilter;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	http
                .formLogin()
                //自定义认证成功处理器
                .successHandler(loginSuccessHandler)
                .failureHandler(loginFailureHandler)//登录失败处理逻辑

                // 自定义登录拦截URI
                .loginProcessingUrl("/login")
                .and()
                //token的验证方式不需要开启csrf的防护
                .csrf().disable()
                // 自定义认证失败类
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                // 自定义权限不足处理类
                .accessDeniedHandler(jwtAccessDeniedHandler)
                .and()
                //设置无状态的连接,即不创建session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/login","/logout","/test/**","/verify/code/**",
                        "/play/playstart/**",
                        "/admin/sys-config/**").permitAll()
                //.antMatchers("/logout").permitAll()
                //.antMatchers("/test/**").permitAll()
                //配置允许匿名访问的路径
                .anyRequest().authenticated();
        http.logout().logoutUrl("/logout")
                .logoutSuccessHandler(jwtLogoutSuccessHandler)
                .invalidateHttpSession(true) ;    //默认为true
/*                .addLogoutHandler(logoutHandler)
                .deleteCookies("cookieNamesToClear");*/
/*
        http.logout(logout -> logout
                         .logoutUrl("/my/logout")
                        .logoutSuccessUrl("/my/index")
                        .logoutSuccessHandler(logoutSuccessHandler)
                        .invalidateHttpSession(true)     //默认为true
                        .addLogoutHandler(logoutHandler)
                        .deleteCookies("cookieNamesToClear")); //一个显式添加CookieClearingLogoutHandler的快捷方式。
*/

/** Logout 的属性：
        invalidate-session  表示是否要在退出登录后让当前session失效，默认为true。
        delete-cookies   指定退出登录后需要删除的cookie名称，多个cookie之间以逗号分隔。
        logout-success-url   指定成功退出登录后要重定向的URL。需要注意的是对应的URL应当是不需要登录就可以访问的。
        success-handler-ref   指定用来处理成功退出登录的LogoutSuccessHandler的引用。
*/

        http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);

        /* 配置自己的jwt验证过滤器，
        意思是在UsernamePasswordAuthenticationFilter 执行前先执行 jwtAuthenticationTokenFilter
         */
    	http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        /**
         * 配置自己的从数据库查询权限拦截器
         * FilterSecurityInterceptor 执行前先执行 cutomFilterSecurityInterceptor
         */
        http.addFilterBefore(cutomFilterSecurityInterceptor, FilterSecurityInterceptor.class);

        /**
         * 防止 退出时找不到 token
         * 先走认证拦截器jwtAuthenticationTokenFilter，再走退出拦截器
         */
        http.addFilterBefore(jwtAuthenticationTokenFilter, LogoutFilter.class);


        // disable page caching
    	http.headers().cacheControl();
    }
    /**
     * 描述: 静态资源放行，这里的放行，是不走 Spring Security 过滤器链
     **/
    @Override
    public void configure(WebSecurity web) {
        //web.ignoring().antMatchers("**");

        web.ignoring()
                .antMatchers("/sse/**")
                .antMatchers("/test/**")
                .antMatchers("/helloworld")
                .antMatchers("/")
                .antMatchers("/#/**")
                .antMatchers("/index.html")
                .antMatchers("/favicon.png")
                .antMatchers("/static/**")
                .antMatchers("/css/**")
                .antMatchers("/fonts/**")
                .antMatchers("/images/**")
                .antMatchers("/jessibuca/**")
                .antMatchers("/jessplayer/**")
                .antMatchers("/js/**")
                .antMatchers("/map/**")
                .antMatchers("/videojs/**")
                .antMatchers("/doc.html/**") // "/webjars/**", "/swagger-resources/**", "/v3/api-docs/**"
                .antMatchers("/webjars/**")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/v3/api-docs/**")
                .antMatchers("/error")
                .antMatchers("/actuator/**")
                .antMatchers("/health")
                .antMatchers("/api/**")
                .antMatchers("/play/playstart/**")
                .antMatchers("/admin/sys-config/**")
                .antMatchers("/auth/**")
        ;
    }
    /**
     * 配置认证方式,
     * 对比用户和密码，然后在userDetailService中组装UserDetail
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // 设置不隐藏 未找到用户异常
        provider.setHideUserNotFoundExceptions(true);
        // 用户认证service - 查询数据库的逻辑
        provider.setUserDetailsService(userDetailService);
        // 设置密码加密算法
        provider.setPasswordEncoder(passwordEncoder());
        auth.authenticationProvider(provider);
    }


    //@Lazy
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 处理URL带双斜杠被权限拦截的问题
     * 在优先最高的拦截里把 多/ 改写为单 /
     * 重新生成 HttpServletRequest， 再走向下一个拦截器
     * @return
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public FilterRegistrationBean setFilter() {
        FilterRegistrationBean filterBean = new FilterRegistrationBean();
        filterBean.setFilter(uriFormatFilter);
        filterBean.setName("uriFormatFilter");
        filterBean.addUrlPatterns("/*");
        filterBean.setOrder(-10000);
        return filterBean;
    }
}
